package ru.ifmo.votinov;

import universum.bi.*;

import java.util.List;
import java.util.Map;
import java.util.HashMap;

public class ConquerorBeing extends CustomBeing{//implements IBeing {
    private static final String OWNER = "giz";
    private static final String NAME = "Conqueror";

    private static final float M0 = 4f;
    private static final float S0 = 3f;

    // states
    private static final int EATING = 1;
    private static final int SEARCHING = 2;
    private static final int DEFENDING = 3;
    private static final int GOING_TO_EAT = 4;
    private static final int GOING_TO_GATHER = 5;
    private static enum customArgNames  {mass}

    // events
    /**
     * make new turn
     */
    static final int E1 = 1;

    /**
     * enemy is near
     */
    static final int E2 = 2;

    /**
     * should become searcher
     */
    static final int E3 = 3;

    /**
     * being died
     */
    static final int E4 = 4;

    /**
     * should go to gather
     */
    static final int E5 = 5;

    private int state;
    private float mass;
    private float speed;
    private float energy;
    private Location colonyLocation;

    private Event actionForCurrentTurn;
    private BeingInterface beingInterface;

    public void reinit(GameKind gameKind, int maxTurns) {
        CorporateMind.createInstance(maxTurns);
    }

    public String getName() {
        return NAME;
    }

    public String getOwnerName() {
        return OWNER;
    }

    public BeingParams getParams() {
        BeingParams result = new BeingParams(M0, S0);
        if (customArgs != null)
        {
        result = new BeingParams(customArgs.get(customArgNames.mass.ordinal()), S0);
        result.customArgs = customArgs;
        }
        else
        {
            int i = 4;
        }
        return result;
    }

    public void processEvent(BeingInterface bi, Event e) {
        beingInterface = bi;
        switch (e.kind()) {
            case BEING_BORN:
                state = EATING;
                mass = ((BeingParams)e.param()).M;
                speed = ((BeingParams)e.param()).S;
                if (customArgs == null) customArgs = ((BeingParams)e.param()).customArgs;
                fireBeingIsBorn();
                break;
            case BEING_DEAD:
                // processing being died event
                processEvent(E4);
                fireBeingIsDead();
                break;
            case BEING_ATTACKED:
                break;
            case BEING_ENERGY_GIVEN:
                break;
        }
    }

    public Event makeTurn(BeingInterface bi) {
        updateInfo(bi);
        // processing make turn event
        processEvent(E1);
        return actionForCurrentTurn;
    }

    void processEvent(int event) {
        switch (state) {
            case EATING:
                switch (event) {
                    // make new turn
                    case E1:
                        // should born
                        if (x1_1()) {
                            // born new being
                            z1();
                        }
                        // should eat
                        else if (x1_2()) {
                            // eat
                            z2();
                        }
                        // should give energy
                        else if (x1_3()) {
                            // give energy
                            z3();
                        }
                        break;
                    // enemy is near
                    case E2:
                        state = DEFENDING;
                        // leave eaters colony
                        z11();
                        break;
                    // should become searcher
                    case E3:
                        state = SEARCHING;
                        // leave eaters colony
                        z11();
                        break;
                    // being died
                    case E4:
                        // leave eaters colony
                        z11();
                        break;
                    // should go to gather
                    case E5:
                        state = GOING_TO_GATHER;
                        // leave eaters colony
                        z11();
                        break;
                }
                break;
            case SEARCHING:
                switch (event) {
                    // make new turn
                    case E1:
                        // being should start new eaters colony.
                        if (x2_1()) {
                            state = EATING;
                            // start new eaters colony.
                            z4();
                        }
                        // being is making it's last possible search step.
                        else if (x2_2()) {
                            state = GOING_TO_EAT;
                            // make last search step.
                            z6();
                        } else {
                            // make search step.
                            z5();
                        }
                        break;
                    // being died
                    case E4:
                        // stop searching
                        z12();
                        break;
                }
                break;
            case DEFENDING:
                switch (event) {
                    // make new turn
                    case E1:
                        // being should attack enemy at the same location
                        if (x3_1()) {
                            // attack enemy at the same location
                            z14();
                        }
                        // being should attack enemy at another location location
                        else if (x3_2()) {
                            // attack enemy at another location
                            z15();
                        } else {
                            state = EATING;
                            // return to eaters colony
                            z13();
                        }
                        break;
                    // being died
                    case E4:
                        // handle defender died.
                        z16();
                        break;
                }
                break;
            case GOING_TO_EAT:
                switch (event) {
                    // make new turn
                    case E1:
                        // being is making it's last step to feeding location.
                        if (x4_1()) {
                            state = EATING;
                            // make last step to feeding location.
                            z7();
                        } else {
                            // make step to feeding location.
                            z8();
                        }
                        break;
                }
                break;
            case GOING_TO_GATHER:
                switch (event) {
                    // make new turn
                    case E1:
                        // being is making it's last step to gathering location.
                        if (x5_1()) {
                            state = EATING;
                            // make last step to gathering location.
                            z9();
                        } else {
                            // make step to gathering location.
                            z10();
                        }
                        break;
                }
                break;
        }
    }

    private void fireBeingIsBorn() {
        getCorporateMind().addBeing(this);
        joinTheColony(getLocation());
    }

    private void fireBeingIsDead() {
        getCorporateMind().removeBeing(this);
    }

    private Location getLocation() {
        return beingInterface.getLocation(this);
    }

    private Location getColonyLocation() {
        return colonyLocation;
    }

    private CorporateMind getCorporateMind() {
        return CorporateMind.getInstance();
    }

    private void joinTheColony(Location location) {
        colonyLocation = location;
        getCorporateMind().joinTheColony(this, location);
    }

    private void leaveOfColony() {
        getCorporateMind().leaveColony(this, colonyLocation);
    }

    private BeingParams getParamsForNewBeing() {
        return new BeingParams(Constants.K_maxbornvariation * mass, speed);
    }

    private void updateInfo(BeingInterface bi) {
        beingInterface = bi;
        actionForCurrentTurn = null;
        getCorporateMind().updateEnemyMasses(bi, this);
        getCorporateMind().checkIfNewTurnStarted(bi, this);
    }

    float getMass() {
        return mass;
    }

    float getSpeed() {
        return speed;
    }

    float getEnergy() {
        return energy;
    }

    float getEnergyPercent() {
        return energy / mass;
    }

    void updateEnergy() {
        energy = beingInterface.getEnergy(this);
    }

    /**
     * Returns whether being should born new being.
     * @return whether being should born new being.
     */
    private boolean x1_1() {
        return getEnergyPercent() > Constants.K_toborn;
    }

    /**
     * Returns whether or not being should eat.
     * @return whether or not being should eat.
     */
    private boolean x1_2() {
        return getCorporateMind().shouldEat(beingInterface, this, colonyLocation);
    }

    /**
     * Returns whether or not being should give energy.
     * @return whether or not being should give energy.
     */
    private boolean x1_3() {
        return getCorporateMind().shouldGiveEnergy(beingInterface, this, colonyLocation);
    }

    /**
     * Returns whether or not being should start new eaters colony.
     * @return whether or not being should start new eaters colony.
     */
    private boolean x2_1() {
        return getCorporateMind().getUnoccupiedEatingLocation(beingInterface, this) != null;
    }

    /**
     * Returns whether or not being is making it's last possible search step.
     * @return whether or not being is making it's last possible search step.
     */
    private boolean x2_2() {
        List<Location> locations = getCorporateMind().getSearchLocations(beingInterface, this);
        if (locations.size() == 0) {
            return !getCorporateMind().canSearchFromLocation(beingInterface, this, getLocation());
        }
        if (locations.size() == 1) {
            Location location = locations.get(0);
            return beingInterface.getReachableLocations(this).contains(location) &&
                    !getCorporateMind().canSearchFromLocation(beingInterface, this, location);
        }
        return false;
    }

    /**
     * Returns whether or not being should attack enemy at the same location.
     * @return whether or not being should attack enemy at the same location.
     */
    private boolean x3_1() {
        Location location = getCorporateMind().getAttackLocationForDefender(beingInterface, this, colonyLocation);
        return location != null && location.equals(beingInterface.getLocation(this));
    }

    /**
     * Returns whether or not being should attack enemy at another location location.
     * @return whether or not being should attack enemy at another location location.
     */
    private boolean x3_2() {
        Location location = getCorporateMind().getAttackLocationForDefender(beingInterface, this, colonyLocation);
        return location != null && !location.equals(beingInterface.getLocation(this));
    }

    /**
     * Returns whether or not being is making it's last step to feeding location.
     * @return whether or not being is making it's last step to feeding location.
     */
    private boolean x4_1() {
        Location location = getCorporateMind().getClosestLocationWithEnergy(beingInterface, this);
        return beingInterface.getReachableLocations(this).contains(location) ||
                location.equals(beingInterface.getLocation(this));
    }

    /**
     * Returns whether or not being is making it's last step to gathering location.
     * @return whether or not being is making it's last step to gathering location.
     */
    private boolean x5_1() {
        Location location = getCorporateMind().getGatheringLocation(beingInterface, this);
        return beingInterface.getReachableLocations(this).contains(location);
    }

    /**
     * Born new being.
     */
    private void z1() {
        actionForCurrentTurn = new Event(EventKind.ACTION_BORN, getParamsForNewBeing());
    }

    /**
     * Eat.
     */
    private void z2() {
        actionForCurrentTurn = new Event(EventKind.ACTION_EAT, mass);
    }

    /**
     * Give energy.
     */
    private void z3() {
        Integer id = beingInterface.getId(getCorporateMind().getEnergyTaker(this, colonyLocation));
        if (id != null) {
            actionForCurrentTurn = new Event(EventKind.ACTION_GIVE, id,
                    getCorporateMind().getAmountToGive(this, colonyLocation));
        }
    }

    /**
     * Start new eaters colony.
     */
    private void z4() {
        getCorporateMind().stopSearching(this);
        Location location = getCorporateMind().getUnoccupiedEatingLocation(beingInterface, this);
        joinTheColony(location);
        actionForCurrentTurn = new Event(EventKind.ACTION_MOVE_TO, location);
    }

    /**
     * Make search step.
     */
    private void z5() {
        makeSearchStep();
    }

    /**
     * Make last search step.
     */
    private void z6() {
        makeSearchStep();
        getCorporateMind().stopSearching(this);
    }

    /**
     * Make last step to feeding location.
     */
    private void z7() {
        Location location = getCorporateMind().getClosestLocationWithEnergy(beingInterface, this);
        joinTheColony(location);
        actionForCurrentTurn = new Event(EventKind.ACTION_MOVE_TO, location);
    }

    /**
     * Make step to feeding location.
     */
    private void z8() {
        Location location = getCorporateMind().getClosestLocationWithEnergy(beingInterface, this);
        location = SearchUtil.stepToward(beingInterface, this, location);
        actionForCurrentTurn = new Event(EventKind.ACTION_MOVE_TO, location);
    }

    /**
     * Make last step to gathering location.
     */
    private void z9() {
        Location location = getCorporateMind().getGatheringLocation(beingInterface, this);
        joinTheColony(location);
        actionForCurrentTurn = new Event(EventKind.ACTION_MOVE_TO, location);
    }

    /**
     * Make step to gathering location.
     */
    private void z10() {
        Location location = getCorporateMind().getGatheringLocation(beingInterface, this);
        location = SearchUtil.stepToward(beingInterface, this, location);
        actionForCurrentTurn = new Event(EventKind.ACTION_MOVE_TO, location);
    }

    /**
     * Leave eaters colony
     */
    private void z11() {
        leaveOfColony();
    }

    /**
     * Stop searching
     */
    private void z12() {
        getCorporateMind().stopSearching(this);
    }

    /**
     * Return to eaters colony
     */
    private void z13() {
        joinTheColony(colonyLocation);
        actionForCurrentTurn = new Event(EventKind.ACTION_MOVE_TO, colonyLocation);
    }

    /**
     * Attack enemy at the same location.
     */
    private void z14() {
        actionForCurrentTurn = new Event(EventKind.ACTION_ATTACK,
                getCorporateMind().getTargetEnemyId(this, getColonyLocation()));
    }

    /**
     * Attack enemy at another location.
     */
    private void z15() {
        actionForCurrentTurn = new Event(EventKind.ACTION_MOVE_ATTACK,
                getCorporateMind().getTargetEnemyId(this, getColonyLocation()));
    }

    /**
     * Handle defender died.
     */
    private void z16() {
        getCorporateMind().removeDefender(this, getColonyLocation());
    }

    private void makeSearchStep() {
        List<Location> locations = getCorporateMind().getSearchLocations(beingInterface, this);
        if (locations.size() == 0) {
            return;
        }
        Location location = locations.get(0);
        if (beingInterface.getReachableLocations(this).contains(location)) {
            locations.remove(location);
        } else {
            location = SearchUtil.stepToward(beingInterface, this, location);
        }
        actionForCurrentTurn = new Event(EventKind.ACTION_MOVE_TO, location);
    }
        protected Map<String, Integer> getCustomArgsNames()
    {
        Map<String, Integer> result = new HashMap<String, Integer>();
        for (customArgNames argname : customArgNames.values())
            result.put(argname.name(), argname.ordinal());
        return result;
    }
    protected Float getCustomArgDefaultValue(int argNum)
    {
        switch (customArgNames.values()[argNum])
        {
            case mass : return 4f;
            default: throw new ArrayIndexOutOfBoundsException("custom argument number " + argNum + " has not default value.");
        }
    }
}
